﻿using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using JamesZhao.Engine.Graphics.Effects;
using JamesZhao.Engine.Graphics.RenderStages;
using JamesZhao.Engine.Graphics.Surfaces;
using SlimDX;
using SlimDX.DXGI;
using Surface = JamesZhao.Engine.Graphics.Surfaces.Surface;

namespace JamesZhao.Engine.Graphics
{
    public class RenderPipeline
    {
        internal RenderPipeline()
        {
            RenderStages = new List<RenderStage>();
            ScreenSizeDependentTextures = new List<Surface>();
        }

        public List<RenderStage> RenderStages { get; private set; }

        public VisualsList VisualsList { get; set; }
        public SwapChain SwapChain { get; set; }
        public GraphicsDevice Device { get; set; }

        public ColorSurface ColorSurface { get; set; }
        public DepthSurface DepthSurface { get; set; }

        public List<Surface> ScreenSizeDependentTextures { get; private set; }


        internal void Initialize()
        {
            SetupStages();
        }

        internal void SetupStages()
        {

            //deferred shade
            if(false)
            {
                var gBuffer = new GBuffer
                {
                    Depth = new ColorSurface(Device, TextureTargetMode.Managed)
                    {
                        Name = "Depth Render Surface",
                        Format = Format.R32_Float
                    },
                    Normal = new ColorSurface(Device, TextureTargetMode.Managed)
                    {
                        Name = "Normal Render Surface",
                        Format = Format.R16G16B16A16_SNorm
                    },
                    Albedo = new ColorSurface(Device, TextureTargetMode.Managed)
                    {
                        Name = "Albedo Render Surface",
                        Format = Format.R8G8B8A8_UNorm
                    }
                };

                var hdrColorBuffer = new ColorSurface(Device, TextureTargetMode.Managed)
                {
                    Format = Format.R16G16B16A16_Float
                };

                ScreenSizeDependentTextures.Add(gBuffer.Albedo);
                ScreenSizeDependentTextures.Add(gBuffer.Depth);
                ScreenSizeDependentTextures.Add(gBuffer.Normal);

                var gBufferGenStage = new GenerateGBufferStage(Device)
                {
                    GBuffer = gBuffer,
                    DepthSurface = DepthSurface,
                };

                var deferredShadingStage = new DeferredShadingStage(Device)
                {
                    ColorSurface = hdrColorBuffer,
                    GBuffer = gBuffer,
                };
                var shadowedDeferredStage = new ShadowedLightsStage(Device)
                {
                    ColorSurface = hdrColorBuffer,
                    GBuffer = gBuffer
                };
                const int samples = 8;
                const int quality = 0;
                const int size = 1024;
                Device.Device.CheckMultisampleQualityLevels(Format.R32_Float, 8);
                shadowedDeferredStage.NativeDepthTexture = new DepthSurface(Device, TextureTargetMode.Managed)
                {
                    BaseSize = new Size(size, size),
                    SampleDescription =
                        new SampleDescription(samples, quality)
                };
                shadowedDeferredStage.MsaaFloatDepthTexture = new ColorSurface(Device, TextureTargetMode.Managed)
                {
                    Format = Format.R32G32_Float,
                    BaseSize = new Size(size, size),
                    SampleDescription =
                        new SampleDescription(samples, quality)
                };
                shadowedDeferredStage.FloatDepthTexture = new ColorSurface(Device, TextureTargetMode.Managed)
                {
                    Format = Format.R32G32_Float,
                    BaseSize = new Size(size, size),
                };
                var postProcessSurfaces = new[]
                                          {
                                              new ColorSurface(Device, TextureTargetMode.Managed)
                                                  {
                                                      Format = Format.R8G8B8A8_UNorm,
                                                      Name = "PP Scratch 0"
                                                  },
                                              new ColorSurface(Device, TextureTargetMode.Managed)
                                                  {
                                                      Format = Format.R8G8B8A8_UNorm,
                                                      Name = "PP Scratch 1"
                                                  }
                                          };
                ScreenSizeDependentTextures.Add(postProcessSurfaces[0]);
                ScreenSizeDependentTextures.Add(postProcessSurfaces[1]);
                var hdr2LdrStage = new Hdr2LdrStage(Device, hdrColorBuffer, postProcessSurfaces);
                var bloomStage = new BloomStage(Device, postProcessSurfaces[1], postProcessSurfaces);
                
            }



            var depthSurface = new ColorSurface(Device, TextureTargetMode.Managed)
                                   {
                                       Name = "Depth Render Surface",
                                       Format = Format.R32_Float
                                   };

            var result0 = new ColorSurface(Device, TextureTargetMode.Managed)
                              {
                                  Name = "Depth Render Surface",
                                  Format = Format.R16G16B16A16_Float
                              };


            var result1 = new ColorSurface(Device, TextureTargetMode.Managed)
                              {
                                  Name = "Depth Render Surface",
                                  Format = Format.R16G16B16A16_Float
                              };


            var clearStage = new ClearStage
                                 {
                                     ClearColor = new Color4(0, 0, 0),
                                     FinalColorSurface = ColorSurface,
                                     FinalDepthSurface = DepthSurface,
                                     Targets = new[] {depthSurface}
                                 };
            ScreenSizeDependentTextures.Add(depthSurface);

            ScreenSizeDependentTextures.Add(result0);
            ScreenSizeDependentTextures.Add(result1);

            var viewspaceDepthStage = new ViewspaceDepthStage(Device)
                                          {
                                              DepthSurface = DepthSurface,
                                              OutputDepth = depthSurface
                                          };
            var presentStage = new PresentStage()
                                   {
                                       SourceSurface = depthSurface,
                                       FinalColorSurface = ColorSurface
                                   };
            var solveStage = new SteeringSolveStage(Device)
                                 {
                                     SceneDepth = depthSurface,
                                     SolveResult = result0
                                 };


            RenderStages.Add(clearStage);
            //RenderStages.Add(new SurfaceVisualizer(Device, "clear", depthSurface));
            RenderStages.Add(viewspaceDepthStage);
            RenderStages.Add(new SurfaceVisualizer(Device, "scene depth", depthSurface));
            RenderStages.Add(solveStage);
            RenderStages.Add(new SurfaceVisualizer(Device, "solve", result0)
                                 {
                                     Scale = 200
                                 });
            
            ColorSurface src = result0;
            ColorSurface target = result1;
            DownsampleStage previous = null;
            for (int i = 0; i < 9; i++)
            {
                var downsampleStage = new DownsampleStage(Device)
                                          {
                                              Source = src,
                                              Target = target,
                                              PreviousDownsample = previous,
                                          };
                RenderStages.Add(downsampleStage);
                RenderStages.Add(new SurfaceVisualizer(Device, "downsample " + i, target)
                                     {
                                         Scale = 150
                                     });

                var temp = src;
                src = target;
                target = temp;
                previous = downsampleStage;


            }

            var cpuReadSurface = new ColorSurface(Device, TextureTargetMode.Raw)
                                     {
                                         Format = Format.R16G16B16A16_Float,
                                         CpuReadable = true,
                                     };
            cpuReadSurface.Mode = TextureTargetMode.Managed;
            ScreenSizeDependentTextures.Add(cpuReadSurface);

            RenderStages.Add(new ReadGpuValueStage
                                 {
                                     RawColorSurface = src, // b/c the forloop switches them
                                     CpuColorSurface = cpuReadSurface
                                 });
        }



        internal virtual GpuProgram CreateShader(string path)
        {
            return new GpuProgram(Device, path);
        }


        public virtual void Render(Camera camera)
        {
            foreach (RenderStage renderStage in RenderStages)
            {
                renderStage.Render(Device, SwapChain, VisualsList, camera);
            }
            //Thread.Sleep(1000);
        }

        public T GetRenderStage<T>() where T : RenderStage
        {
            return (T) RenderStages.First(x => x is T);
        }


        public void OnResize(Size size)
        {
            foreach (Surface renderTarget in ScreenSizeDependentTextures)
            {
                renderTarget.BaseSize = size;
            }
        }
    }
}